#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
static char* ReadEntireFileIntoMemoryAndNullTerminate(const char* FileName) {
    char* Result = 0;
    FILE* File = fopen(FileName, "r");
    if (File) {
        fseek(File, 0, SEEK_END);
        size_t FileSize = ftell(File);
        fseek(File, 0, SEEK_SET);
        Result = (char*)malloc(FileSize + 1);
        fread(Result, FileSize, 1, File);
        Result[FileSize] = 0;
        fclose(File);
    }
    return Result;
}

enum token_type {
    Token_Unknown,
    Token_Identifier,
    Token_OpenParen,
    Token_Colon,
    Token_String,
    Token_CloseParen,
    Token_Semicolon,
    Token_Asterisk,
    Token_OpenBracket,
    Token_CloseBracket,
    Token_OpenBraces,
    Token_CloseBraces,
    Token_EndOfStream,
};
struct token {
    token_type Type;
    size_t TextLength;
    char* Text;
};

struct tokenizer {
    char* At;
};

inline bool IsEndOfLine(char C) {
    bool Result = (C == '\n') || (C == '\r');
    return Result;
}

inline bool IsWhitespace(char C) {
    bool Result = ((C == ' ') || (C == '\t') || (C == '\v') || (C == '\f') || IsEndOfLine(C));
    return Result;
}

inline bool IsAlpha(char C) {
    bool Result = (((C >= 'a') && (C <= 'z')) || ((C >= 'A') && (C <= 'Z')));
    return Result;
}
inline bool IsNumber(char C) {
    bool Result = ((C >= '0') && (C <= '9'));
    return Result;
}

static void EatAllWhitespace(tokenizer* Tokenizer) {
    for (;;) {
        if (IsWhitespace(Tokenizer->At[0])) {
            ++Tokenizer->At;
        } else if ((Tokenizer->At[0] == '/') && (Tokenizer->At[1] == '/')) {
            Tokenizer->At += 2;
            while (Tokenizer->At[0] && !IsEndOfLine(Tokenizer->At[0])) {
                ++Tokenizer->At;
            }
        } else if ((Tokenizer->At[0] == '/') && (Tokenizer->At[1] == '*')) {
            Tokenizer->At += 2;
            while (IsEndOfLine(Tokenizer->At[0]) &&  //
                   !((Tokenizer->At[0] == '*') &&    //
                     (Tokenizer->At[1] == '/'))) {
                ++Tokenizer->At;
            }
            if (Tokenizer->At[0] == '*') {
                Tokenizer->At += 2;
            }

        } else {
            break;
        }
    }
}

static token GetToken(tokenizer* Tokenizer) {
    //
    EatAllWhitespace(Tokenizer);
    token Token;
    Token.TextLength = 1;
    Token.Text = Tokenizer->At;
    char C = Tokenizer->At[0];
    ++Tokenizer->At;
    switch (C) {
        case '\0': {
            Token.Type = Token_EndOfStream;
        } break;
        case '(': {
            Token.Type = Token_OpenParen;
        } break;
        case ')': {
            Token.Type = Token_CloseParen;
        } break;
        case ':': {
            Token.Type = Token_Colon;
        } break;
        case '*': {
            Token.Type = Token_Asterisk;
        } break;
        case '[': {
            Token.Type = Token_OpenBracket;
        } break;
        case ']': {
            Token.Type = Token_CloseBracket;
        } break;
        case ';': {
            Token.Type = Token_Semicolon;
        } break;
        case '{': {
            Token.Type = Token_OpenBraces;
        } break;
        case '}': {
            Token.Type = Token_CloseBraces;
        } break;
        case '/': {
        } break;
        case '"': {
            Token.Type = Token_String;
            Token.Text = Tokenizer->At;
            while (Tokenizer->At[0] && Tokenizer->At[0] != '"') {
                if ((Tokenizer->At[0] == '\\') && Tokenizer->At[1]) {
                    ++Tokenizer->At;
                }
                ++Tokenizer->At;
            }
            Token.Type = Token_String;
            Token.TextLength = Tokenizer->At - Token.Text;
            if (Tokenizer->At[0] == '"') {
                ++Tokenizer->At;
            }
        } break;
        default: {
            if (IsAlpha(C)) {
                Token.Type = Token_Identifier;
                while (IsAlpha(Tokenizer->At[0])      //
                       || IsNumber(Tokenizer->At[0])  //
                       || Tokenizer->At[0] == '_') {
                    ++Tokenizer->At;
                }
                Token.TextLength = Tokenizer->At - Token.Text;
            }
#if 0
            else if (IsNumeric(Tokenizer->At[0])) {
                ParseNumber();
            }
#endif
            else {
                Token.Type = Token_Unknown;
            }
        } break;
    }
    return Token;
}

inline bool TokenEquals(token Token, const char* Match) {
    const char* At = Match;
    for (size_t Index = 0; Index < Token.TextLength; ++Index, ++At) {
        if ((*At == 0) || (Token.Text[Index] != *At)) {
            return false;
        }
    }
    bool Result = (*At == 0);
    return Result;
}

static bool ParseIntrospectionParams(tokenizer* Tokenizer) {
    bool Valid = true;
    for (;;) {
        token Token = GetToken(Tokenizer);
        if (TokenEquals(Token, "IGNORED")) {
            Valid = false;
            break;
        }
        if ((Token.Type == Token_CloseParen) || (Token.Type == Token_EndOfStream)) {
            break;
        }
    }
    return Valid;
}

static void ParseMember(tokenizer* Tokenizer, token StructTypeToken, token MemberTypeToken) {
#if 1
    bool Parsing = true;
    bool IsPointer = false;
    while (Parsing) {
        token Token = GetToken(Tokenizer);
        switch (Token.Type) {
            case Token_Asterisk: {
                IsPointer = true;
            } break;
            case Token_Identifier: {
                printf("{%s, MetaType_%.*s, \"%.*s\", (size_t)&((%.*s *)0)->%.*s},\n",       //
                       IsPointer ? "MetaMemberFlag_IsPointer" : "MetaMemberFlag_NoPointer",  //
                       (int)MemberTypeToken.TextLength, MemberTypeToken.Text,                //
                       (int)Token.TextLength, Token.Text,                                    //
                       (int)StructTypeToken.TextLength, StructTypeToken.Text,                //
                       (int)Token.TextLength, Token.Text);
            } break;
            case Token_Semicolon:
            case Token_EndOfStream: {
                Parsing = false;
            } break;
            default: {
            } break;
        }
    }
#else
    token Token = GetToken(Tokenizer);
    switch (Token.Type) {
        case Token_Asterisk: {
            ParseMember(Tokenizer, Token);
        } break;
        case Token_Identifier: {
            printf("DEBUG_VALUE(%.*s)\n", (int)Token.TextLength, Token.Text);
            std::cout << std::endl;
        } break;
    }
#endif
}

static bool RequireToken(tokenizer* Tokenizer, token_type DesiredType) {
    token Token = GetToken(Tokenizer);
    bool Result = (Token.Type == DesiredType);
    return Result;
}

struct meta_struct {
    char* Name;
    meta_struct* Next;
};

static meta_struct* FirstMetaStruct;

static void ParseStruct(tokenizer* Tokenizer) {
    token NameToken = GetToken(Tokenizer);
    if (RequireToken(Tokenizer, Token_OpenBraces)) {
        printf("member_definition MembersOf_%.*s[] = \n", (int)NameToken.TextLength,
               NameToken.Text);
        printf("{\n");
        for (;;) {
            token MemberToken = GetToken(Tokenizer);
            if (MemberToken.Type == Token_CloseBraces) {
                break;
            } else {
                printf("    ");
                ParseMember(Tokenizer, NameToken, MemberToken);
            }
        }
        printf("};\n");
        meta_struct* Meta = (meta_struct*)malloc(sizeof(meta_struct));
        Meta->Name = (char*)malloc(NameToken.TextLength + 1);
        memcpy(Meta->Name, NameToken.Text, NameToken.TextLength);
        Meta->Name[NameToken.TextLength] = 0;
        Meta->Next = FirstMetaStruct;
        FirstMetaStruct = Meta;
    }
}

static void ParseIntrospectable(tokenizer* Tokenizer) {
    if (RequireToken(Tokenizer, Token_OpenParen)) {
        if (ParseIntrospectionParams(Tokenizer)) {
            token TypeToken = GetToken(Tokenizer);
            if (TokenEquals(TypeToken, "struct")) {
                ParseStruct(Tokenizer);
            } else {
                fprintf(stderr, "ERROR: Introspection is only support for structs right now");
            }
        }
    } else {
        fprintf(stderr, "ERROR:Missing parentheses");
    }
}
#define ArrayCount(Array) (sizeof(Array) / sizeof((Array)[0]))
int main() {  //
    printf("#pragma once \n #include \"game_meta.h\"\n");
    const char* FileNames[]{
        "../../../../game/game_sim_region.h",  //
        "../../../../game/game_math.h",        //
        "../../../../game/game_world.h",
        "../../../../game/game_platform.h",

    };
    for (unsigned long long FileIndex = 0; FileIndex < ArrayCount(FileNames); ++FileIndex) {
        char* FileContents = ReadEntireFileIntoMemoryAndNullTerminate(FileNames[FileIndex]);
        tokenizer Tokenizer = {};
        Tokenizer.At = FileContents;
        bool Parsing = true;
        while (Parsing) {
            token Token = GetToken(&Tokenizer);
            switch (Token.Type) {
                case Token_EndOfStream: {
                    Parsing = false;
                } break;
                case Token_Unknown: {
                } break;
                case Token_Identifier: {
                    if (TokenEquals(Token, "introspect")) {
                        ParseIntrospectable(&Tokenizer);
                    }
                } break;
                default: {
                } break;
            }
        }
    }
    printf("#define META_HANDLE_TYPE_DUMP(MemberPtr, NextIndentLevel) \\\n");
    for (meta_struct* Meta = FirstMetaStruct; Meta; Meta = Meta->Next) {
        printf(
            "    case "
            "MetaType_%s:{DebugTextLine(Member->Name);DEBUGDumpStruct(ArrayCount(MembersOf_%s),"
            "MembersOf_%s,MemberPtr,"
            "NextIndentLevel);} "
            "break; %s\n",
            Meta->Name, Meta->Name, Meta->Name, Meta->Next ? "\\" : "");
    }
}